{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "cell_id": "57654ce51cf5479db3e9250a67bc21be",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 25
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: tqdm in d:\\dell\\appdata\\anaconda3\\lib\\site-packages\n",
      "Requirement already satisfied: importlib-resources; python_version < \"3.7\" in d:\\dell\\appdata\\anaconda3\\lib\\site-packages (from tqdm)\n",
      "Requirement already satisfied: colorama; platform_system == \"Windows\" in d:\\dell\\appdata\\anaconda3\\lib\\site-packages (from tqdm)\n",
      "Requirement already satisfied: zipp>=3.1.0; python_version < \"3.10\" in d:\\dell\\appdata\\anaconda3\\lib\\site-packages (from importlib-resources; python_version < \"3.7\"->tqdm)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "You are using pip version 9.0.1, however version 24.2 is available.\n",
      "You should consider upgrading via the 'python -m pip install --upgrade pip' command.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集大小： 100000\n",
      "用户数：943，电影数：1682\n",
      "[215  47  42  19 139 170 320  47  18 156]\n",
      "[371 109  70 172  70  21 308 158 240  68]\n"
     ]
    }
   ],
   "source": [
    "!pip install tqdm\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from tqdm import tqdm # 进度条工具\n",
    "\n",
    "data = np.loadtxt('movielens_100k.csv', delimiter=',', dtype=int)\n",
    "print('数据集大小：', len(data))\n",
    "# 用户和电影都是从1开始编号的，我们将其转化为从0开始\n",
    "data[:, :2] = data[:, :2] - 1\n",
    "\n",
    "# 计算用户和电影数量\n",
    "users = set()\n",
    "items = set()\n",
    "for i, j, k in data:\n",
    "    users.add(i)\n",
    "    items.add(j)\n",
    "user_num = len(users)\n",
    "item_num = len(items)\n",
    "print(f'用户数：{user_num}，电影数：{item_num}')\n",
    "\n",
    "# 设置随机种子，划分训练集与测试集\n",
    "np.random.seed(0)\n",
    "\n",
    "ratio = 0.8\n",
    "split = int(len(data) * ratio)\n",
    "np.random.shuffle(data)\n",
    "train = data[:split]\n",
    "test = data[split:]\n",
    "\n",
    "# 统计训练集中每个用户和电影出现的数量，作为正则化的权重\n",
    "user_cnt = np.bincount(train[:, 0], minlength=user_num)\n",
    "item_cnt = np.bincount(train[:, 1], minlength=item_num)\n",
    "print(user_cnt[:10])\n",
    "print(item_cnt[:10])\n",
    "\n",
    "# 用户和电影的编号要作为下标，必须保存为整数\n",
    "user_train, user_test = train[:, 0], test[:, 0]\n",
    "item_train, item_test = train[:, 1], test[:, 1]\n",
    "y_train, y_test = train[:, 2], test[:, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "cell_id": "eb7e34b4066c44c584f557951884b425",
    "collapsed": true,
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 37
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [],
   "source": [
    "class MF:\n",
    "    \n",
    "    def __init__(self, N, M, d):\n",
    "        # N是用户数量，M是电影数量，d是特征维度\n",
    "        # 定义模型参数\n",
    "        self.user_params = np.ones((N, d))\n",
    "        self.item_params = np.ones((M, d))\n",
    "        \n",
    "    def pred(self, user_id, item_id):\n",
    "        # 预测用户user_id对电影item_id的打分\n",
    "        # 获得用户偏好和电影特征\n",
    "        user_param = self.user_params[user_id]\n",
    "        item_param = self.item_params[item_id]\n",
    "        # 返回预测的评分\n",
    "        rating_pred = np.sum(user_param * item_param, axis=1)\n",
    "        return rating_pred\n",
    "    \n",
    "    def update(self, user_grad, item_grad, lr):\n",
    "        # 根据参数的梯度更新参数\n",
    "        self.user_params -= lr * user_grad\n",
    "        self.item_params -= lr * item_grad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "cell_id": "e4c6edbad5dd4d33924f22998316f8ac",
    "collapsed": true,
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 49
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [],
   "source": [
    "def train(model, learning_rate, lbd, max_training_step, batch_size):\n",
    "    train_losses = []\n",
    "    test_losses = []\n",
    "    batch_num = int(np.ceil(len(user_train) / batch_size))\n",
    "    with tqdm(range(max_training_step * batch_num)) as pbar:\n",
    "        for epoch in range(max_training_step):\n",
    "            # 随机梯度下降\n",
    "            train_rmse = 0\n",
    "            for i in range(batch_num):\n",
    "                # 获取当前批量\n",
    "                st = i * batch_size\n",
    "                ed = min(len(user_train), st + batch_size)\n",
    "                user_batch = user_train[st: ed]\n",
    "                item_batch = item_train[st: ed]\n",
    "                y_batch = y_train[st: ed]\n",
    "                # 计算模型预测\n",
    "                y_pred = model.pred(user_batch, item_batch)\n",
    "                # 计算梯度\n",
    "                P = model.user_params\n",
    "                Q = model.item_params\n",
    "                errs = y_batch - y_pred\n",
    "                P_grad = np.zeros_like(P)\n",
    "                Q_grad = np.zeros_like(Q)\n",
    "                for user, item, err in zip(user_batch, item_batch, errs):\n",
    "                    P_grad[user] = P_grad[user] - err * Q[item] + lbd * P[user]\n",
    "                    Q_grad[item] = Q_grad[item] - err * P[user] + lbd * Q[item]\n",
    "                model.update(P_grad / len(user_batch), Q_grad / len(user_batch), learning_rate)\n",
    "                \n",
    "                train_rmse += np.mean(errs ** 2)\n",
    "                # 更新进度条\n",
    "                pbar.set_postfix({\n",
    "                    'Epoch': epoch,\n",
    "                    'Train RMSE': f'{np.sqrt(train_rmse / (i + 1)):.4f}',\n",
    "                    'Test RMSE': f'{test_losses[-1]:.4f}' if test_losses else None\n",
    "                })\n",
    "                pbar.update(1)\n",
    "\n",
    "            # 计算 RMSE 损失\n",
    "            train_rmse = np.sqrt(train_rmse / len(user_train))\n",
    "            train_losses.append(train_rmse)\n",
    "            y_test_pred = model.pred(user_test, item_test)\n",
    "            test_rmse = np.sqrt(np.mean((y_test - y_test_pred) ** 2))\n",
    "            test_losses.append(test_rmse)\n",
    "    \n",
    "    return train_losses, test_losses"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "cell_id": "f9c3bdf9f8134ae191f480013f31b916",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 61
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████████████████████████| 37500/37500 [00:51<00:00, 730.21it/s, Epoch=29, Train RMSE=0.9673, Test RMSE=1.0048]\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x27326cf7ef0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 超参数\n",
    "feature_num = 16 # 特征数\n",
    "learning_rate = 0.1 # 学习率\n",
    "lbd = 1e-4 # 正则化强度\n",
    "max_training_step = 30\n",
    "batch_size = 64 # 批量大小\n",
    "\n",
    "# 建立模型\n",
    "model = MF(user_num, item_num, feature_num)\n",
    "# 训练部分\n",
    "train_losses, test_losses = train(model, learning_rate, lbd, max_training_step, batch_size)\n",
    "\n",
    "plt.figure()\n",
    "x = np.arange(max_training_step) + 1\n",
    "plt.plot(x, train_losses, color='blue', label='train loss')\n",
    "plt.plot(x, test_losses, color='red', ls='--', label='test loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('RMSE')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "cell_id": "49e35d9ebe1f4722aba0b8a2d3283441",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 73
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 2.57712395  3.48622005  3.76150216  3.58604004  4.8058418   3.47284112\n",
      "  3.37246031  4.0917956   3.02605747  3.45742155]\n",
      "[2 4 4 4 5 2 3 1 4 4]\n"
     ]
    }
   ],
   "source": [
    "y_test_pred = model.pred(user_test, item_test)\n",
    "print(y_test_pred[:10]) # 把张量转换为numpy数组\n",
    "print(y_test[:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "cell_id": "bb235f3ac34d403cb7e561122d8f4714",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 103
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集大小: 800\n",
      "测试集大小: 200\n",
      "特征数: 24\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn import metrics # sklearn中的评价指标函数库\n",
    "from tqdm import tqdm\n",
    "\n",
    "# 导入数据集\n",
    "data = np.loadtxt('fm_dataset.csv', delimiter=',')\n",
    "\n",
    "# 划分数据集\n",
    "np.random.seed(0)\n",
    "ratio = 0.8\n",
    "split = int(ratio * len(data))\n",
    "x_train = data[:split, :-1]\n",
    "y_train = data[:split, -1]\n",
    "x_test = data[split:, :-1]\n",
    "y_test = data[split:, -1]\n",
    "# 特征数\n",
    "feature_num = x_train.shape[1]\n",
    "print('训练集大小:', len(x_train))\n",
    "print('测试集大小:', len(x_test))\n",
    "print('特征数:', feature_num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "cell_id": "a2fe9d6caf574c71833830292d742d28",
    "collapsed": true,
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 115
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [],
   "source": [
    "class FM:\n",
    "\n",
    "    def __init__(self, feature_num, vector_dim):\n",
    "        # vector_dim代表公式中的k，为向量v的维度\n",
    "        self.theta0 = 0.0 # 常数项\n",
    "        self.theta = np.zeros(feature_num) # 线性参数\n",
    "        self.v = np.random.normal(size=(feature_num, vector_dim)) # 双线性参数\n",
    "        self.eps = 1e-6 # 精度参数\n",
    "        \n",
    "    def _logistic(self, x):\n",
    "        # 工具函数，用于将预测转化为概率\n",
    "        return 1 / (1 + np.exp(-x))\n",
    "\n",
    "    def pred(self, x):\n",
    "        # 线性部分\n",
    "        linear_term = self.theta0 + x @ self.theta\n",
    "        # 双线性部分\n",
    "        square_of_sum = np.square(x @ self.v)\n",
    "        sum_of_square = np.square(x) @ np.square(self.v)\n",
    "        # 最终预测\n",
    "        y_pred = self._logistic(linear_term + 0.5 * np.sum(square_of_sum - sum_of_square, axis=1))\n",
    "        # 为了防止后续梯度过大，对预测值进行裁剪，将其限制在某一范围内\n",
    "        y_pred = np.clip(y_pred, self.eps, 1 - self.eps)\n",
    "        return y_pred\n",
    "    \n",
    "    def update(self, grad0, grad_theta, grad_v, lr):\n",
    "        self.theta0 -= lr * grad0\n",
    "        self.theta -= lr * grad_theta\n",
    "        self.v -= lr * grad_v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "cell_id": "e624d0064abc4464a471f7587a9ccc0d",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 127
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|████████| 200/200 [00:26<00:00,  7.64it/s, 训练轮数=199, 训练损失=11.3006, 训练集准确率=0.816, 测试集准确率=0.785]200 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试集准确率：0.79，\t测试集AUC：0.7201320910484726\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 超参数设置，包括学习率、训练轮数等\n",
    "vector_dim = 16\n",
    "learning_rate = 0.01\n",
    "lbd = 0.05\n",
    "max_training_step = 200\n",
    "batch_size = 32\n",
    "\n",
    "# 初始化模型\n",
    "np.random.seed(0)\n",
    "model = FM(feature_num, vector_dim)\n",
    "\n",
    "train_acc = []\n",
    "test_acc = []\n",
    "train_auc = []\n",
    "test_auc = []\n",
    "\n",
    "with tqdm(range(max_training_step)) as pbar:\n",
    "    for epoch in pbar:\n",
    "        st = 0\n",
    "        while st < len(x_train):\n",
    "            ed = min(st + batch_size, len(x_train))\n",
    "            X = x_train[st: ed]\n",
    "            Y = y_train[st: ed]\n",
    "            st += batch_size\n",
    "            # 计算模型预测\n",
    "            y_pred = model.pred(X)\n",
    "            # 计算交叉熵损失\n",
    "            cross_entropy = -Y * np.log(y_pred) - (1 - Y) * np.log(1 - y_pred)\n",
    "            loss = np.sum(cross_entropy)\n",
    "            # 计算损失函数对y的梯度，再根据链式法则得到总梯度\n",
    "            grad_y = (y_pred - Y).reshape(-1, 1)\n",
    "            # 计算y对参数的梯度\n",
    "            # 常数项\n",
    "            grad0 = np.sum(grad_y * (1 / len(X) + lbd))\n",
    "            # 线性项\n",
    "            grad_theta = np.sum(grad_y * (X / len(X) + lbd * model.theta), axis=0)\n",
    "            # 双线性项\n",
    "            grad_v = np.zeros((feature_num, vector_dim))\n",
    "            for i, x in enumerate(X):\n",
    "                # 先计算sum(x_i * v_i)\n",
    "                xv = x @ model.v\n",
    "                grad_vi = np.zeros((feature_num, vector_dim))\n",
    "                for s in range(feature_num):\n",
    "                    grad_vi[s] += x[s] * xv - (x[s] ** 2) * model.v[s]\n",
    "                grad_v += grad_y[i] * grad_vi\n",
    "            grad_v = grad_v / len(X) + lbd * model.v\n",
    "            model.update(grad0, grad_theta, grad_v, learning_rate)\n",
    "\n",
    "            pbar.set_postfix({\n",
    "                '训练轮数': epoch,\n",
    "                '训练损失': f'{loss:.4f}',\n",
    "                '训练集准确率': train_acc[-1] if train_acc else None,\n",
    "                '测试集准确率': test_acc[-1] if test_acc else None\n",
    "            })\n",
    "        # 计算模型预测的准确率和AUC\n",
    "        # 预测准确率，阈值设置为0.5\n",
    "        y_train_pred = (model.pred(x_train) >= 0.5)\n",
    "        acc = np.mean(y_train_pred == y_train)\n",
    "        train_acc.append(acc)\n",
    "        auc = metrics.roc_auc_score(y_train, y_train_pred) # sklearn中的AUC函数\n",
    "        train_auc.append(auc)\n",
    "\n",
    "        y_test_pred = (model.pred(x_test) >= 0.5)\n",
    "        acc = np.mean(y_test_pred == y_test)\n",
    "        test_acc.append(acc)\n",
    "        auc = metrics.roc_auc_score(y_test, y_test_pred) \n",
    "        test_auc.append(auc)\n",
    "            \n",
    "print(f'测试集准确率：{test_acc[-1]}，\\t测试集AUC：{test_auc[-1]}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "cell_id": "a0f21191b7da4a109e2748dda3d89fa6",
    "deepnote_app_coordinates": {
     "h": 5,
     "w": 12,
     "x": 0,
     "y": 139
    },
    "deepnote_cell_type": "code"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxIAAAFACAYAAAAoHUoeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XmczfX3wPHXO/uWfalkqWSZQWRtJVmSpI1Iu1ZUiqSI\n1K9Fm5QWlWhDEl9FtpDKTiprxlLGILJk7Mz5/XHmmjtjjDtj7v3cO/c8H4/7uPP53M+998w17udz\n3st5OxHBGGOMMcYYYzLjDK8DMMYYY4wxxkQeSySMMcYYY4wxmWaJhDHGGGOMMSbTLJEwxhhjjDHG\nZJolEsYYY4wxxphMs0TCGGOMMcYYk2mWSBhjjDHGGGMyzRIJY4wxxhhjTKZZImGMMcYYY4zJtNxe\nB5BdSpUqJZUqVfI6DGOMCVtLlizZISKlvY7Da3a+MMaYjAV6vsgxiUSlSpVYvHix12EYY0zYcs79\n5XUM4cDOF8YYk7FAzxc2tMkYY4wxxhiTaZZIGGOMMcYYYzLNEgljjDHGGGNMpuWYORLpOXLkCPHx\n8Rw8eNDrUCJO/vz5KV++PHny5PE6FGOMCTo7X2QfO38YEz1ydCIRHx9PkSJFqFSpEs45r8OJGCLC\nv//+S3x8PJUrV/Y6HGOMCTo7X2QPO38YE11y9NCmgwcPUrJkSTspZJJzjpIlS1rLnDEmatj5InvY\n+cOY6JKjEwnATgpZZJ+bMSba2Pde9rDP0ZjokeMTCWOMMcYYY0z2s0QiiHbv3s27776bpee2bt2a\n3bt3Z3NExhhjwpEX54vatWvTsWPHVPuaNGmSarG+jRs3Ehsbe3x74cKFXHHFFVStWpVq1arRpUsX\n9u/fn6W4jTGRL0dPtvaa78Tw8MMPn/DYsWPHyJUr10mfO3ny5GCGZsLchg0wY0bqfQUKwM03Q/78\n3sRkjAmeUJ8vVq1aRVJSEnPmzGHfvn0UKlTolM/Ztm0bt9xyC6NHj6Zx48aICOPGjWPv3r0ULFgw\n0zFEqpkzITYWypTxOhJjvGc9EkH01FNPsW7dOi666CJ69erF7Nmzadq0KZ06daJmzZoAtGvXjosv\nvpiYmBiGDRt2/LmVKlVix44dbNy4kerVq3PfffcRExNDixYtOHDgwAnv9e2339KwYUPq1KnD1Vdf\nzbZt2wBITEzk7rvvpmbNmtSqVYtx48YBMGXKFOrWrUvt2rVp1qxZCD4NE6iffoK6deH++1Pfbr8d\nHn/c6+iMMcEQyvMFwJdffsntt99OixYtmDhxYkAxDh06lDvvvJPGjRsDOhfi5ptvpmzZsqf520eO\n33+HZs3gggvgwQfhqadg0yavozLGO1HTI/HYY7BsWfa+5kUXweDBJ3/85ZdfZvny5SxLfuPZs2ez\ncOFCli9ffrws3vDhwylRogQHDhygfv363HTTTZQsWTLV66xdu5ZRo0bx4Ycf0r59e8aNG0fnzp1T\nHXPZZZcxf/58nHN89NFHDBo0iNdff53nn3+eokWL8scffwCwa9cutm/fzn333cecOXOoXLkyO3fu\nzMZPxZyOcePgttugYkWYPRtKlUp57IUX4IMPoGtXiInxLERjcrycfr4AGDNmDNOnT2fNmjW88847\nJwxxSs/y5cu58847M/Fb5zzffAPOQZMmMGoU7NsHU6fq9/WMGdC8OZx55onPO3oU/v4bRNJ/3dy5\noUIFfW1jIknUJBLhokGDBqlqaw8ZMoTx48cDsGnTJtauXXvCiaFy5cpcdNFFAFx88cVs3LjxhNeN\nj4+nQ4cObNmyhcOHDx9/jxkzZjB69OjjxxUvXpxvv/2WK6644vgxJUqUyNbfMRINHw6JidC9e+Bf\n5OvWwTPPwH//nfyYBg2gXz842aiEb7+F997Tn48e1RNRw4a63z+JAHj+eT1xde8O338P+fIFFqcx\nJjIF63yxaNEiSpcuTcWKFSlfvjz33HMPu3btonjx4ulWXLIqTCnGj4dLLwVfJ87kyXDttXDOOZpU\nPP44vP566ud8/73uX70649d+6CHI4jQZYzwTNYlERi1BoeQ/DnX27NnMmDGDefPmUbBgQZo0aZJu\n7e18fleMuXLlSrerunv37jz++OO0bduW2bNnM2DAAEAXB0p7EkhvXzRLTIRHHtGTwOLFcPXVejFf\ntSrExWlicf75qZ+zeDG0bg2HD8OFF6b/uocPw3PPacvmjTdC7dp6++sv2LULypfX4UqFCulJCODe\ne+GttyC94calSukJqksXuOYauOsubeWsVStbPw5jol5OP1+MGjWK1atXU6lSJQD+++8/xo0bR5cu\nXShZsiS7du06fuzOnTspldyqERMTw5IlS7j++uuz61eMKOvX69Am/0ShdWt4+WWYMAGSkuCzz3Tb\nt6j3pEnQpo2eJ959FwoXTv+1Z8/WRqWYGB06VbWqnnv27NEejrSn7GPHYMQImDYNqlfXc0na85Qx\noRA1iYQXihQpwt69e0/6+J49eyhevDgFCxZk9erVzJ8/P8vvtWfPHs5JvhodOXLk8f0tWrTgnXfe\nYXDymXHXrl00btyYrl27smHDhuNDm6K5V+KbbzSJuOUWPQl89pleyPfsCa++CiVKwJ9/6mTnI0fg\nhx/02NKldT5D1aonf+233oIePeB//9Ou6z594O23NXlp0EDv583TE0Eg7r1XeyLuuQdmzdKfV6+G\n5OuB40Ssi9xHRP/dAnHGGfrv5P9csM/SBF+ozhdJSUmMHTuW33///fg5Y9asWbzwwgt06dKFJk2a\n8Pnnn3P11VfjnGPkyJE0bdoUgG7dutGgQQOuvfZaGjZsCHD82HLlymUpnnA3fz4MG6bfBevX674b\nbkh9TO/eevMlDd99B8WKQUKC9iDXrq3f8wUKnPx9OnbUhqtu3XT7zjuhVy9o3Biuv17PJR066LkI\nYP9+2LEDypWDsWM1ufnoIz3GmFCyydZBVLJkSS699FJiY2Pp1avXCY+3atWKo0ePUqtWLfr160ej\nRo2y/F4DBgzglltu4fLLLz/eegTQt29fdu3aRWxsLLVr12bWrFmULl2aYcOGceONN1K7dm06RPk3\nz4gROnFuzBjYsgX++ANq1ICBA3XM6ubNOoTpiiv0wr11a6hSBebOzTiJAHj0Udi2TS/2L79chyeV\nLauvNXeuTqIONInw6dwZtm6FRYv0wrdPn5THDh1KaZlKnhYT1bZsgUaN9N8tkFvhwvD++/rcnTvh\nqqs04UtI8Pb3MDlfqM4Xc+bM4ZxzzjmeRABcccUVrFy5ki1btnD//fdTpEgRateuTe3atUlMTKRn\nz54AlC1bltGjR9OzZ0+qVq1K9erV+emnnzgzvUkBOcCBA1opb+xYrdS0caNeqPuNNkulZUs46yzo\n1Em/Ozp31gTk668zTiJAGzAmTdJ5ct26wciRep44eBA+/1x7n+fM0bkZV10F112n56yEBO3lrlUL\nbr1Vh0etWKFJhjGh4ORkM38iTL169cS/9jVoebvqmb1KM8fl1M9vyxZ4+mntMhbRLunnn4e+fVOO\nSUyEr76C9u11CNG4cVp29fHHteTf3XenP6EuI4cOwZdfQtu2UKSIngTatdOfs+rZZzX2tm11Hsa6\nddr1XqKEzrn44QeoVy/rrx9Ofv9dhwz4RnMULaoT0P2uh1JZvRpatdIT6uOPB1Y2d/ZsmD5dJ0zG\nxelJOnduKFkSpkzJfNIXbpxzS0Qkh/xFZJ2dL4IvJ3yer74KTz6p3wtXXhnYc157DYYO1fNJo0b6\n/VSsWObeNylJG6ymT9fb66/rPIsxY7Q3PD1Hjuh7Dhqk24UKwa+/aqOXMVkR6PnCEglzUjn187vt\nNm0h8vUmFC6s22efnf7xGzdqC9HTT8Mll4QszIAkJurvs2GDbufOrSe+Sy7R21lnwYIF2nORXf77\nT09uR49mfFzZstp6lh1mztThBLly6dwS0KSpZEn4v/+DvHlTH793rw418LXyBZpMHT2q5RynTdPh\nbYMG6d+Hbz7Mt9/qRMtIZYmEsvNF8EX657ltG1Srpt+jkyaF/v0PHtSehqpV9ecNGwJryFi8WIc/\n3X+/TgIfMyb4sZqcKeDzhYjkiNvFF18saa1cufKEfSZwOfHzmz9fBESeecbrSILv00/1d/3ss+x7\nzb//FomJ0dcN5Najh8ixY6f3nqNGieTJo+/7998p+5cuFSlX7uTvfcEFInFxp/fePuvWiVSpIpI/\nv8j48dnzml4AFksYfF97fbPzRfBF8ud55IhI06YiBQqIrFjhdTRZ8+yz+j24cKHXkZhIFej5wiZb\nm6ixdatWPCpbVlurc7rbbtMJej17Qp06p7/2xPLlOlRo714tgXiq+SHvvQdvvqm3MmW0ZSy9HgoR\nbfnv31+Hf6Xniit0CFrx4in76tSBtWtPvhhU5crZtwr4eefpnJY2bU6caBmoq67Sno4MFig2xngo\nPl7nN/z1l675MGKEzpeLRE88ofO97r1XJ4xH0cLjJsQskTA5moheTC9YoBeCO3boRfDpzEuIFGec\noSfC5s3hsss0CTj/fF3XIqM1KA4e1LK1/uXnRXSOQMGCOuGvdu1Tv/9bb+mwgFWrdLJiy5ZafSTt\nhfSOHbp+Rps2uqJ3WsWL6wqy6SUFhQuHbt5CqVI6xOr993V+TWZs3aqVXz75RJNZY0x4OXxY58T9\n8Yd+Fz3xhFZOilRnnqkTtlu31knfrVvrOcA3NNSY7GKJhMmxjhyB++7TL9NKlbQn4uuvoX59ryML\nndhYLTt4zz2wZAmMHq0J1SOPaEnTXLl0EmGRIrB0qbbCvfGGlrWtUiV12dOLL9aF+ypWDOy9ndMq\nIqDVq7p0gd9+S//Yvn01ecnOuRzBULCgTtzOLBGtpNK3ryYk1arpzRjjvY0b9Ttx3jztOW3f3uuI\n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\nw2++0Qvu/Pl14nrt2tn7fsWL6+2zz3SYU/fuJx6TO7cmN5demrnXdk4rVoXKa6/pMOy77sra83v1\n0qFZkcASCeMZEVi2DJKnfKQyfLiOu7z7bp0gFxXjDI2JTvGA/xTL8kDCSY69Fejqv0NEEpLv1zvn\nZqPzJ05IJERkGDAMtEfitKM2ITdzJvTtq6W+ffbu1Vbs8ePh/PO9iy0a3HijnpvPPjv7kwh/bdro\n6IP0rg3y5YuMhsR8+XTh0UmTUsrtBmrCBHjzTXjggcj4m87xiYRIzqtYFAqhGPLm63E4mb59tQKC\n/fMZk6MtAqo45yoDm9FkoVPag5xzVYHiwDy/fcWB/SJyyDlXCrgUyHI7np0vskcwzh+vvabVhM47\nL6WH2qd3b2jXLtvf0qTj7rtD8z6FC0f++h7FisFtt2X+ec2baxLy6KNaTevSS6Fs2eyPL7vk6EQi\nf/78/Pvvv5QsWdJODpkgIvz777/H16EIhk2btF5z27ZaFSKt0qV1zKsxJmcTkaPOuW7AVLT863AR\nWeGcG4hO9puYfGhHYLSkvkqtDnzgnEsCzkDnSKwkC+x8kT2Ccf744AMd6tG+vbaIW/Ulk5OddZYm\nx/37a49GoUJazapUKV07K71eiu3bdUHBw4d1qF/VqqGLN0dPtj5y5Ajx8fHHy6WawOXPn5/y5cuT\nJ0hjiu64A776CtasSX/ClDEm+4XjZGsv2PkiuLLz/DF/vrbIXnONDvnInaObP41JsWYN7NypvXHj\nx+tw8EqVdBHe4sVTjjtyRNe+mDtXt88+W9exKFPm9N7fJlsDefLkoXLlyl6HYdJYvFgnUz31lCUR\nxpjwYOeL8COiPRFlymidf0siTDTx9SqMG6fzLBYt0rU92rbVe5/lyzWJ+PJLXX/jkkv0mGbN4OKL\ndW5LMNl/SxNSIvDEEzp0qU8fr6MxxhgTriZN0tWI33sv8sfLG3M6cuWCRo1g6FB47DHtqfNxThtm\nO3bU7Q8/1JK8S5bonJZgJxIZrDV4+pxzrZxza5xzcc65p9J5vIJzbpZz7lfn3O/OudZ+j/VJft4a\n51zLYMZpQuf992HOHJ1EfeaZXkdjjDEmHO3erZNNL7hAVyQ2xuhciX37dDiT73b4MLz0UsoxnTvr\nat5HjsCwYcGPKWg9Es65XMBQoDla3m+Rc25imolwfYGvROQ951wNYDJQKfnnW4EY4GxghnPuwjS1\nw02EeeUVzZpbtkx/oRljjDFGRFtS//4bfvzRyn8bE86C2SPRAIgTkfUichgYDaRdDkQAX7t0UVJq\nh1+PVuc4JCIbgLjk1zMRKiEBnn5aF7OZONHGuhpjjEnfiBE6sXrQIB3vbYwJX8FMJM4BNvltxyfv\n8zcA6Oyci0d7I3zrGAbyXBNBvvhCVx598UXIm9fraIwxxoSjXbt0vYhLL9Wx4MaY8BbMduH0CnGn\nrTXbERghIq875xoDnznnYgN8Ls65+4H7ASpUqHCa4ZpgEdEWpksugQsv9DoaY4wx4SAhAR56KPUK\nxgkJWvJy6FBbjNSYSBDMRCIeONdvuzwpQ5d87gVaAYjIPOdcfqBUgM9FRIYBw0Drgmdb5Oa0fP21\n1j/22b0bVq4MzaQfY4wxkWHECB3q2rBhStJQuDC89RbUru1paMaYAAUzkVgEVHHOVQY2o5OnO6U5\n5m+gGTDCOVcdyA9sByYCXzrn3kAnW1cBFgYxVpOBAwegQIHAjo2P19VH065zWLas7jfGGJPzbN2q\nK+v+9puWa/VfMOtkxo+HBg1Sl7I0xkSWoCUSInLUOdcNmArkAoaLyArn3EBgsYhMBJ4APnTO9UCH\nLt0lutT2CufcV8BK4CjQ1So2eWPzZm0Z6toVnnvu1Md/9pkmEWvWgP/aTrlywRlBLTZsjDHGCyJw\n1VWwapVuV6yoVfoysmmTLk7qX7bSGBN5nKRtOo5Q9erVk8WLF3sdRo5z993a/Zw3L6xenTo5SEtE\nV1UsV05L9hljwotzbomI1PM6Dq/Z+SJ7LVsGderAu+/CvHkwdiysXQvly5/8Oe+8A92763nFt4Kv\nMSZ8BHq+sDZic1K//gojR8Ltt2uPQq9eJw5Z8jd3Lvz5J9x1V8hCNMYY47Hx47XH+aabdLHRpCS4\n5RZNMPwdOaLDX2fMgMGDoXp1SyKMiXSWSJiTeu01KFoUhgyBPn1g3Dh4+GE45jfITAS2bNEkon17\nKFECbr7Zu5iNMcaE1oQJWq61TBmoVAk+/lh7JOrX1+FLAHv3Qt26cO650Ly5rsb79tuehm2MyQaW\nSJh07dkD33wDnTpBsWLwzDPQuze8/z7066fHHDwIHTrA2WfrScQ5HdJUpIi3sRtjjAmN9evh99/h\nhhtS9nXurPPkihXT84YIdOmicygGDYIvv9THmzXzLm5jTPaw9YVNur76ShMF3zClM86Al1/WGt+v\nv67d1o8+Cj/9pCeKCy+E1q11foQxxpicTwQefxxy54Ybb0z9WMmS0LevLipXvz4sWaITsHv18iZW\nY0xwWCID9ad6AAAgAElEQVRhTuBbQK56daiXZprNiy/qOhH16+u8idGjtVfCGGNMdEhM1HPEr7/C\n//4Hb76plZrSevBBnVS9YYMOke3aNeShGmOCzBIJc9zBg/Dff9C/v855ePPNE1cWLV9eHx80SBOK\npk29idWYqLRmDZx3HuTJ43UkJor17q0VmgDuuEN7p9OTLx8sWqQ9FoULhy4+Y0zo2BwJA+iQpcqV\ndeG499/XE8Ujj6R/bO/e8M8/lkQYE1JJSXDZZfDAA15HYqJYXBwMG6Z/hvv3a2W/tA1O/ooVsyTC\nmJzMeiQMoBOod+6EN97QtSCuuSbj43PlCk1cxphkv/4KO3boyl/GeKRvX11XaMAAKFDA62iMMV6z\nRMKwbBl88olOmuvRw+tojDHpmjJF71u08DYOE7WWLIExYzSZsMIaxhiwRCLqicATT+j6D337eh2N\nyVZHjujElwIFdJBydjh6FA4c0J+dy94xCyI6izOtvHl1sHVSEuzbd+Lj+fLpMad6PBLs3Zt6O1cu\nKFhQf54wAS6+WIv1G+OBp57SakxWeckY42NzJKLcpEkwc6ZOoC5WzOtoTLbZu1cX+DjzTB2r5ltF\ncP9+TSreeSfzrykCl1yir3nmmbpgyCuvnH6sO3fqfWJiymv73156SR/fujX9x32rWq1dm/7jI0bo\n44cOaTWBcNWv34mx33KLPiaiK3u1auVtjCZqTZ+uK1L37at/msYYA9YjEdWOHNGWpQsv1DJ9JgeZ\nOVPH019zDUybptV+atTQBOPYMejZE7p1y9xrOqcLhwwZoguLDB+uC4707p31ONevhwsugM8/1yXR\nX3vtxGMaN9b7M89M//Err9T70qXTf7xRo5TEqnfv8O16K1ZMP4NGjVL2Va6s987pcsHXX+9NbCaq\nJSVpb0TFivDQQ15HY4wJyMcf62rB1aoF9W0skYhiw4bB6tVaB9yqSeYwU6bosKNRo3S7aFG9L1tW\nJ8K8+672TviGzQQqX76UcQ2HD+vrHzwI+fNnLc6pU7W1vV49HX70xBMnP7Zw4YwfL1Ei48erVtXP\nJVwTiYxiB7jnntDEYQza3uAbxThhAixdCp9+ql8Bxpgw9++/upz8oEFBTyRsaFOUOXxYbzt2aNWN\npk3huuu8jspkuyef1NUCixZNSSIAVq2Cli11mM+PP2buNTt00LJePr17w++/Zz2JAL2wr1wZqlTJ\n+msEqlUrmD8fdu8O/ntl1tat2kVoTBhISNAOzCJF9Hb77VCzJnTq5HVkxpiAFCqk59ebbgr6W1ki\nEUVeeEFbk/Ll01Eg//4Lr7+ecQ1wE6EqV4Zrr9Wff/pJS4YuW6ZXBytX6gTsqVMDf71//4WxY1PP\nMTgj+etDJGsxHj6sQ7BatQrNH2GrVtrM+sMPwX+vzLr/fl0u3hiP7dkDrVvD5s3wf/8Hr76qIwa/\n+87KfhsTMfLn10bD884L+lvZ0KYoERcHAwdC8+bQpInuq1NHbybCdeumTYg+W7Zol+bdd+vF/hln\nwKxZKRN3r7lG5xXUqAE//5y6l8HnlVe0l2DGDB0GtX27JgxpJ/sOHqxDpdq1S0kG/Cc4v/IKLFig\ntSKHDEldPWruXJ1g3bJltn0UGWrUSHtnMttKs3cvdO+uSVSuXJpQgU5Ynzkz9bGFC+v4D9ArsHnz\nUj9eqpSOKQT9D7lsmf48fboNXTKeE4E774QVK7QQh1UaNiZCjR+vJdauuCLob2WJRJTo3VuHoI8c\nCWed5XU0Jlv99Zfe/P34I9x7r/7csCG0aaPHdOigcwV8F/2JiZplpnXwoN7v2ZPy+HXXndhqXreu\nZqPr1qXsK1Ei5ectW3T40/jxcOutqb/UqleHoUNDt8Ba7ty6bHtmh1FNnKj/capVS70C1z//nPjZ\n+Zez2br1xMf9y9tu2ZLyeI0acNddmYvLmGyydy+MGweLFumcucGDLYkwJqL17g2xsSFJJJxkdVhC\nmKlXr54sXrzY6zDCTlKSDpd//XV4/vnwnWdqMikxUUttPfaYTlQOZ3v2aEt8r17w4oteR5N5t9+u\nvRjbtqUM54pQzrklIhLmfzDBZ+eLFMeO6VCmadN0u1MnLaJmQ16NiVD//ae976d50Rfo+cJ6JHKo\nH3/Uxudt23S7Wzfo08fbmEw2mj0bvvgiMlqxixbV9SemTk1JJP75ByZP1nKmxYuHLhYRTQry59dK\nA4Ec/+OPOvwqwpMIY9Lzf/+nScSQITr6sWxZSyJMssOH9fuvefPMP/fIES0P7iv9VbgwtG+fPd+j\nv/6qy6yndfvtOgl04ULtCU/rnnv0/X/5RQuP+MuVS4cDg55f8+VLKT0ejtL+Du3aaYPdqlV6bQA6\nYiAELJHIgaZN01Eo550HDzygIzJuvdVODjnK1KlauvWyy7yOJDCPPqqlwkT0D3HyZP3SXrYstImE\nc7qGxjnnBJZIOKdfzHv2BD82Y0Ls8GF4+WVdvqRbNztHmDSefVbnuc2bl3p9m0D89BN07px637Fj\ncNttpx/XpEm6gGdaN92kCcC4cVr2NK277tJE4osv4L33Uj+WP39KItGvHyxfrkVGwrEB6dgxna/o\nP1S2bl1NJH78UVsHChQIWQEPG9qUA7VpA3/8oUm7/3B1E4HmztWJzgULprQKzZ2rX8YxMVpKJZLM\nnq0X5W+/rTM6ExJCf/XyxBM6UXr0aD1JXHWV1riMi9OY/BUpoglHDrnCsqFNys4Xav58bXT9+uuQ\nVIk0kebmm/WifO9e7VHIrF279LnO6aSbUqU0wcgKEXjrLf1DLVo0dQVBn7PP1u/0PXv0fdM65xyN\nZdcu2LfvxMfLl9f7V1/VMeFr1+qCqeFm5Uo9/w8ZAjfcoPvKlNGJsImJWuK8SJHUpd+zwIY2RSkR\nbTy44QZLIiLepk26KiVApUqwYYP+PGAAbNwYmWPVevRIqVR0333eXKC3bauVqm68UbdXr9YJ6BMn\nnrgo3PnnQ//+2mVuTA7zyy967/uaMSaVv//WhpasJBGgvc2+HudRo3RpdJH0S4Y7pzffY2nPDXPn\n6vkjb154+OHUhS3SSrt+UkZxpefqq/V+6dLwTCQKF9a5D9dem5L8+D+W1X+vLLJEIof580/YuTO8\nh/aZAK1erffDh0ODBin7331XqyrVqOFNXKdj1KiUMbPVq3sTw5VX6me7f79uV6yo9507pz/cqXLl\n0MVmTAj9/LNeJ5Ur53UkJuwcOaLzDOrX13NQZspTi+gM/s6dU4YyXXSR3n/8sZYnT2vlSj0nvPmm\nNjb5ymj7vPuuXvzfeWfWfp/MiImBPHk0kWjfPvjvl1kVKuhE6jBhiUQO4ytbf8kl3sZhsoGvNGjL\nltpl6xOOLSSBqlbN6whU1aon7itTRm/GRAERTSTatPE6EuO5HTt0zQHndGhM4cK6ImHRojq8p1s3\nvYAPdEXC+HgtanHddSc+VrcuPPfciftLldL7n3/WIbC++XQ+v/yiw6MKFcr0r5dpefPqUu4rVwb/\nvbLi99+1gatIEa8jASyRiFiLF+v//Zo1ddifz9y5UKxY+tdJJsIUK6arB9rCH8aYbPbnn3oOsWFN\nUU4EmjXTNQdatYKuXTV5qFRJ18L59FOdpLxmTeC94EuX6n16VYNOtRJuixa67tBff2kMoJOe//pL\nhzSFytSp4Tk+PCkJLr9ce3uGDvU6GgDCcDq6yUhSkq4zUr++LlBcs6b+H/PxFVcIx0IDJpM6dtQV\nqXPIRF9jTPiYPl3vLZGIcjNnagt3s2a6eGliog4jAj33+NYp8iUHgVi6VC9CatXKfDy+5MP//eLi\ntKpSiMqZAtpDEo4XUuvW6UTzUH4Wp2A9EhGmTx+tavbgg5pI3HADDByoxQy2btWiM7fc4nWUJluk\n7do1xphM+uUXLbwDcMcdOlT9wAEt+9qwYfiMNjRB9tJLur7Cq6/q8Nhp07QE6h9/6JDOTp30Yv26\n6/SCYtAgveB4+mktJTp0aEo5V99r+StXLqWk6ocf6nyHggUzH2fNmjqEaunSlIIYDRumX4UpmLZv\n19+/c2cdGRCon37SeR6+SeOxsSnzGR5+GLZsSX18/fr6GYOWn929O/XjV1yhk8xB6/hv2qQ/WyJh\nsuLPP7XYzN13a4OBc3D//frzvffqfa5c2pBtIlxSkn4x9+59YiUhY3Ig51wr4C0gF/CRiLyc5vE3\nAd9s+IJAGREplvzYnYBvCdcXRGRkaKIOXyI6P3bECL0+FNGff/oJJkzQIfBffGFtFVHh4EFdE6J0\naTh0SPf99x+sX69zDp5+Wv9IQI974AGdbH3uuZA7t14A+y8At3WrPtffkSMpP197rSYEWVGggCYz\nxYql3p87xJerhQvrf5izzw48kRCBxx7THhTfsKySJVMe37RJK2H5O/fclJ//+iv1EBNInelv2KD/\nlq1ba4ISJmwdiQjSrp32Qq5dq6uPgi4QXKuWLiy0Z4/OiXrrLW/jNNlg0yatzPD++/qlbkw2CNd1\nJJxzuYA/geZAPLAI6Cgi6c52dM51B+qIyD3OuRLAYqAeIMAS4GIR2XWy94uG88X48dqg+9hj8MIL\n2sDauLFeA4L2aE+e7G2MJkQWL9aW73HjUlr5I0mHDrqOUnrVnoKpZk2t6hfoek2bNmmX30svaStv\nhLN1JHKYZcvgf//TBQt9SQRoj+TcuTpHyjltTDA5gK9iUyRXaDImcA2AOBFZD+CcGw1cD5ysbEpH\noH/yzy2B6SKyM/m504FWwKigRhzGjh7VRuZq1XQkS+7c2vD8ww/ayHrBBTpKwkQJX29CGA2HOaVt\n22DBAl3F+auvsjbf4nTVrZsymWjRIp0knju3jiH3revkr00b7XEIde+Jx6Lrt41gn3yiFckefPDE\nx847T1ex/u+/1L1oJoJZImGiyznAJr/teKBhegc65yoClYGZGTz3nLTPiyaff65LpYwfn/qapkYN\nHfpuoszSpboGg2/NnHC3bp1WjdmxI2WfF4tj1a2rVat+/lnnKiQk6JDjjz6CwYNTH+ucDkkORXna\nMGOJRAQ4fBi+/BKuv/7k1cgKFYrKv9+c67ff9Aog7aqVxuRM6Y3UP9m421uBr0XkWGae65y7H7gf\noEKFClmJMSKIwJAhOirj+uu9jsaEhRdf1KE2kTIh5vzzdTJPYqJuFyjgzQKsF18MF16YMnnaN7+k\nR4+UhfaMJRKRYPJkTczvusvrSEzItG2rg5kDXQDImMgWD/jNOqQ8kHCSY28FuqZ5bpM0z52d9kki\nMgwYBjpHIuuhhreFC7WH+r33Iue60QRZyZKRN1whHMqJXXaZdu3FxGiPhK9Hp0IFvRnA1pEIe3Fx\nWrTnrLN0nRaTww0bphO1WrSAr7/2OhpjQmURUMU5V9k5lxdNFiamPcg5VxUoDszz2z0VaOGcK+6c\nKw60SN4XlYYO1QVvrcHUAFpd6YUXdFiOybzff4dVq6wcZgasRyKM7dypiwUdO6ZFA6Js/k50eu89\nKFpU61cbEyVE5KhzrhuaAOQChovICufcQGCxiPiSio7AaPErNygiO51zz6PJCMBA38TraLJ9Ozz0\nkBbm6d5dkwkTpUqWTBkWlJSks+9tgams8a3xcPPN3sYRxuzSNIx9/72Wd50zR+cdmRxuyxYtz/Xi\ni15HYkzIichkYHKafc+m2R5wkucOB4YHLbgIMGAATJyoC5T27Ol1NCbkPv1UJyk/95zWgT98OOWx\ns8/Wsf4m815/He67T1e6NumyRCKMTZmif7uXXup1JCboDhzQuoygtXyNMSZAx47pSMh27aBfP6+j\nMZ745htdZOq55/Rmske1auExXyOM2RyJMJWUBFOnQsuWcIb9K+V8GzZoF+pZZ0Ht2l5HY4yJIHPm\naO91+/ZeR2I8s369VjsyJsTsEjVMLVumY16tcTpKnHuurjg4e7ZljsaYgBw6pBWaPv8cChaE1q29\njsh4QsQSCeMZG9oUpiZN0nur1BQFli/XUq9t2lgSYYwJiIgWkhk/Xrfbt9dkwkShbdtg3z5LJIwn\nLJEIQ19+Cc8/D1ddBWXKeB2NCbqPPtLb3r1eR2KMiRBvvqlJxKOPapGeDh28jsh4Zts2XbnaEgnj\nAUskwoiIFgjo1QuaNNEyfiYKrFsHF1xgq0cZYwLy88/w5JNwww2aUNhXR5SrXVvrxUuOXWfRhDEb\nRxEmkpLg8cc1iWjfXis2FSvmdVQmJOLiNJEwxphT+Ocf7X2oVAk++cSSCOPH/hiMByyRCANHj+pY\n18GD4bHHYNQoyJfP66hMSBw7ppPkLJEwxgRg4EDYsUPLvRYt6nU0JiwMHKgXD8Z4wIY2hYEPPoCv\nvoJXXtHuahNF4uN14SAb22qMOYWDB+GLL3SR3Ysu8joaEzamToW8eb2OwkSpoPZIOOdaOefWOOfi\nnHNPpfP4m865Zcm3P51zu/0eO+b32MRgxuml3bt1RdImTXRYk4kyZ50FixdD27ZeR2KMCXMTJug5\n4557vI7EhIXp07URasECa4wynglaj4RzLhcwFGgOxAOLnHMTRWSl7xgR6eF3fHegjt9LHBCRHN3m\nkpQEPXvCv//qJGsb3hiF8uaFiy/2OgpjTAQYPhwqVoSmTb2OxISFceO0dPgdd8BDD3kdjYlSwRza\n1ACIE5H1AM650cD1wMqTHN8R6B/EeMJKUhJ07qzzIXr3hrp1vY7IZLvly7XLuUgRuP9+3ff11/DX\nXynHLFwI112nfwzGGHMSEydqA/Tzz9tyMybZI4/AtdfqOcQYjwQzkTgH2OS3HQ80TO9A51xFoDIw\n0293fufcYuAo8LKITEjnefcD9wNUqFAhm8IOjTlzNIno1w+ee87raExQPPAAzJ0LFSqkJBIffgjT\npqU+7p9/LJEwxpzUhg1w553a4NSzp9fRmLBRo4bejPFQMNs10huoc7Iix7cCX4vIMb99FUSkHtAJ\nGOycO2EAoIgME5F6IlKvdOnSpx9xCM2dq/c9etiQphxp1y6YPx+eegpWrEjZP348/Pdf6tuMGd7F\naYwJe++8AwcOwNixkD+/19GYsBAfrzPvd+3yOhIT5YKZSMQD5/ptlwcSTnLsrcAo/x0ikpB8vx6Y\nTer5ExFv3jyoXl0XozQ50IwZOn6tTRsoXDhlf8GCOtTJ/5Yrl3dxGmPC3h9/QGwsnHee15GYsDFj\nhvZkb9vmdSQmygUzkVgEVHHOVXbO5UWThROqLznnqgLFgXl++4o75/Il/1wKuJSTz62IOCKaSDRu\n7HUkJmg2boQyZaBhuqP5jDEmYL5Ewpjjli6FQoWgShWvIzFRLmiJhIgcBboBU4FVwFcissI5N9A5\n51/rsiMwWiTV2u7VgcXOud+AWegciRyRSBw9CmvXaqUmSyRysF69YNMmyG1LtRhjsm7HDi3MY4mE\nAbSne/VqbY286CLr0TaeC+pVjohMBian2fdsmu0B6TxvLlAzmLF5YcECuPpqqJM8SOuSS7yNx5ym\npCRYskTHp6W3MrUtEGSMOU2+KVY1c9wZ0WTJihU6927xYlvN2oQFKyIXIiL6f/7AAfjpJyhWDKpV\n8zoqc1oOHIDLLoP330+9f/Bg3X/ggDdxGWNyjD/+0HvrkTCAZpSvvKKlxPtHTcV8E8Zs3EWIjB2r\nRXw++gh27tTGaqsFHuH+/BMaNYIpU+C111L2f/cd7NkDBQp4F5sxJqItWQLffw9//60NT2ef7XVE\nxnMiWi48NtYySxM2LJEIkfffh6pV4a67bEhjjrBnD9SvD3nywMGDOh/i3HNh3z7tcure3esIjTER\n6uhRLcizerV+xTRqZGXCDbBunU6uHjlSV7M2JgxYm3gIiMCvv0LTppZE5Bg//ADHjsEbb+i2b5G5\n2bPh8GFo1cqz0IwxkW3ECE0iatWCI0es8dkkm5dc3PKii7yNwxg/lkiEwN9/w+7dULu215GY07Zr\nF3ToAL17w5lnQpcucM45cOiQPj5mjA5puuwyb+M0xkSkY8dgwADthfjlF2jfXm/GMH++rj0UE+N1\nJMYcZ0ObQmDZMr23RoQcIC4O5syBfPk0mciTRytotGunjzdtCuefb8vPGmOyZMMG2LwZnntO17Ic\nM8briExIbNkCE09YagtatIDKlbVFcto0aNDAhjaYsGKJRJAcParVQfPk0UTCOSvflyPUr69f+P66\ndUv5+e67QxuPMSZHWb1a72vU8DYOE2Lr1sGDD564/5tvNJFYsUIbsu67L/SxGZMBSySCIC5Ox7Ye\nOABt22oSceGFugiliWDHjunN1ocwxgTJqlV6b+XBo0yDBpCQcOL+4sX1vmlTXZmwTJnQxmXMKdgc\niSCYM0eTiJtv1p7KSZNsfkSOsGABlCwJP//sdSTGmBxi+3ZYuDBle/VqKFs25frR5HDbt8MDD2gS\ncdZZJ958w2Tz59c/DCvfZcKMJRJBsGyZjm398kuttnH0qM2PiCjr16csJ7t/P3z1ld6GDtVtm+hm\njMkmAwbAJZfoQsWgPRLWGxFFRoyAYcP03GJMBLJEIgiWLdOhTXnyaHVQ56yIT0S5+Wb44AP9eedO\nrdLUoYNmhldeaU2FxphsM3++jpi8+24t/rZ6NVSv7nVUJmTmztWxzzYpxkQomyORzZKSNJG4/Xbd\nbt4cduyAEiW8jcsEKCFBF/1o0UK3y5ZN6Z0AqFjRm7iMMTnOwYPw++/QsKGOnOzVSytMW49EFFm6\nFC691OsojMmyU/ZIOOe6OeesCTZAGzfC3r2phzJZEhFBfAvL3Xqr3ufJoy1FvpvNmDfGZJPfftOh\nr08+qRWk335b91siESV27NCyrnXreh2JMVkWyNCmcsAi59xXzrlWztlMn4zYmhERbsoUKFfOZscb\nY4Ju0SK9r18fXnwRzkg+I9vQphxozpyUTNEnPh7Kl7dEwkS0UyYSItIXqAJ8DNwFrHXOveicOz/I\nsUWk337Tk0FsrNeRmEw7dgymT4eWLa0yhjEm6BYt0tGT5ctr8tCli1b3LF/e68hMthKBp5/WCk3+\nLroINm3S0q7GRKiAJluLiABbk29HgeLA1865QUGMLSL98gtUrQoFCngdiTmpZct0vFmBAim3Bx/U\n5GHCBOjRw+sIjTFRYNEi7Y3wtVsMHapTss6wMig5y6xZenFQsaImFWlZw5WJYKecbO2cewS4E9gB\nfAT0EpEjzrkzgLXAk8ENMXL8+CP88AP83/95HYnJ0LhxOpGlR4+UL/B69fTsffnl3sZmjIkKO3Zo\nhSbfdCyA3LmhVCnvYjLZbPduHdL0xhtQurQmEhdcAAMHao3411+H66+HJ57wOlJjsiyQqk2lgBtF\n5C//nSKS5JxrE5ywIk9SEjz+OJx7rjVoh71+/eDGG6FOHa8jMcZEqS++0MbpG2/0OhITNDNnwh13\nwL598PzzOn5t82bo3DnlmHbtvIvPmGwQSCIxGdjp23DOFQFqiMgCEVkVtMgizOefaxW3zz+3YU1h\nL29eSyKMMZ4RgY8/1mFNNp8uB7vxRp0HkZioC5nmygVr12p3FOi2LXBqIlwgicR7gH9JgX3p7Itq\n+/frPKp69aBjR6+jMRmaNAlmz9blZK2UqzHGA0uXwh9/wHvveR2JCZrly7UH4rzzUu8/91y9GZND\nBJJIuOTJ1sDxIU22kJ2fN97Q3spRo2ySXFh64QUtswc64e2ff+CVV7yNyRhznHOuFfAWkAv4SERe\nTueY9sAAQIDfRKRT8v5jwB/Jh/0tIm1DEvRpGD4c8udPPT/CRLiYGNi6NWV7zx546KETS74ak8ME\nkhCsT55w7Ws7eRhYH7yQIs+YMVq9zebphqlZs1KvTv3gg5bxGRMmnHO5gKFAcyAeXbdooois9Dum\nCtAHuFREdjnnyvi9xAERiZiVew4ehC+/hJtugmLFvI7GnJbNm2HDBmjQAG64QZMHn1y54JFHvIvN\nmBAJJJF4EBgC9EVbgn4A7g9mUJHk4EGtvNE27NvAotCkSbqw3A8/eB2JMebkGgBxIrIewDk3Grge\nWOl3zH3AUBHZBSAi/4Q8ymwyYYIW87nnHq8jMadt3Dh49FFISNCeb2OiUCAL0v0jIreKSBkRKSsi\nnSL5Szy7rVwJR4/aStZhZ/9+bfJ74w2vIzHGZOwcYJPfdnzyPn8XAhc6535xzs1PHgrlk985tzh5\nf9iXwBk+HCpVgiZNvI7EnLalS6FcOTjrLK8jMcYzgawjkR+4F4gB8vv2i4i1p6Brm4ElEmHnxx/h\n0CFo1erUxxpjTotzriVQRES+TrP/NuAfEZme0dPT2Zd21a7cQBWgCVAe+Mk5Fysiu4EKIpLgnDsP\nmOmc+0NE1qUT4/0k96ZXqFAhwN8se+3cCTNmaAVqG12ZAyxdCnWt7oyJboF8lX0GlANaAj+iX+J7\ngxlUJFm2TIv/nH++15FEkW3bYN681LfFi1MeX71ai7Tnz28TV4wJjefQ80NaPwADT/HceMC/jE15\nICGdY/4nIkdEZAOwBk0sEJGE5Pv1wGwg3drOIjJMROqJSL3SpUufIqTgWLlSS782auTJ25vsdOCA\n/oNaImGiXCBzJC4QkVucc9eLyEjn3JfA1GAHFimWLdNh+Na6FELNm2vtRH9lymiCAdC7N0ycCNde\na4t6GBMaBUVke9qdIrLVOXeqOsuLgCrOucrAZuBWoFOaYyYAHYERzrlS6FCn9c654sB+ETmUvP9S\nYNBp/i5Bsyp55aXq1b2Nw2SDP/6AY8cskTBRL5BE4kjy/W7nXCywFagUtIgiiAj89lvqRSpNCCxc\nCN99B0WKpOzLmzfl5/794eGH4eKLQx+bMdEpv3Mut4gc9d/pnMsDZJjNi8hR51w3tIEqFzBcRFY4\n5wYCi0VkYvJjLZxzK4FjQC8R+dc5dwnwgXMuCe1hf9m/2lO4Wb1aO0o9GlllsmL//hMbrkCzwV9+\nsazQRL1AEolhya0+fYGJQGGgX1CjihAbN8J//9n8iJDLnx9uvvnkj1sLkTGh9g3woXOum4jsA0ju\niaQSUv0AACAASURBVBiS/FiGRGQyMDnNvmf9fhbg8eSb/zFzgZqnHX2IrF4NVataD3ZEefxx+OCD\nE/fHx8Mll4Q+HmPCTIaJhHPuDOC/5JJ7c4DzMjo+2thEaw8sXgyffgp9+lilDGPCR1/gBeAv59xf\n6ATqc4GPsYan41atgoYNvY7CZMqQIXDVVal7wAFKlvQmHmPCTIaJRPIq1t2Ar0IUT0RZtkxblmJj\nvY4kisybpyuFPvOM15EYY5IlD2l6yjn3HHBB8u44ETngYVhh5cAB7cW+806vIzGZkjcvtG/vdRTG\nhK1AOlinO+d6OufOdc6V8N2CHlkEWLZMu6ltPm82mz8fYmLgwgvh449TPxYXB4UL6+RqY0xYcM7d\n6Jy7EbgGraZ0AVDPOVck42dGj7VrdV5dtWpeR2ICtmIF9OgBmzad+lhjolQgcyR860V09dsn2DAn\nli2DSy/1OoocaORI2LBB5zqck2Zdqrg4uOACcOmVnjfGeOS6dPaVAGo55+4VkZmhDijcWMWmCDRn\nDgweDI895nUkxoStUyYSIlI5FIFEmp074e+/oWvXUx9rMkEEpkyBFi1gwoQTH4+Lg1q1Qh+XMeak\nROTu9PY75yqiQ2OjfmbAqlXa/lGliteRmIAtXQolSliZLWMyEMjK1nekt19EPs3+cCLHb7/pvU20\nzmZr1+pA4t69NalYswaSkqBGDd0+dEh7JIwxYU9E/kouARv1Jk3SNYdsKGwE8a1cbT3gxpxUIEOb\n6vv9nB9oBiwFLJFATwwmG513ntbm9iULTZtCkyYwapR+mW/cqAmFMSbsOeeqAYe8jsNrv/+uBefe\nesvrSMxJ/fqr9oI3bgytWkFioiYSTz7pdWTGhLVAhjZ19992zhUFPgtaRBFi6VKtPlq2rNeR5DC5\nc6euzd2yJfzvf/Dii7p9/fU6EdsYEzacc9+ic+f8lQDOAqJ+yc6PP9biP7fd5nUkJl0icPvtKZOr\nW7XSnvC8eXWYrTHmpALpkUhrP1qVI2q9/z588QV07Oh1JDnMwYPw9NNw770pyUKHDvDZZynlXitV\nskTCmPDzWpptAXaiyURnYF7IIwoTe/bA55/DDTfY0gNBkZSkK8PmyQOFCmXtNaZP1yRi5Ei4I3k0\n95ln6lBaY0yGApkj4d/SdAZQgyheV2LiRHjoIWjTJv3FLs1pmDMH3nwTmjdPSRauuUa/zJOSdDtX\nLu/iM8akS0R+9P3snLsI6AS0BzYA47yKy2si2i6yZ48ukGyCoH17GDdOG5lmzoTK6dSH2br1xKQg\nb96URU1feEF/vvXWoIdrTE4TSI+Ef0vTUeAvEYkPUjxh7fBheOIJLd/3zTfaAGKy0dSpkC8fXHll\n6v25s9JxZowJFefchcCtQEfgX2AM4ESkqaeBhdjKlbq2kK+947339Br3tdegQQNvY8uRVq3SD7h9\nexg0CCpWTP+4jh1h9uzU++rWhSVLtJFq61bo1k2TC2NMpgRyhfY3sEVEDgI45wo45yqJyMagRhaG\n3ntPq49OmmRJRFBMmaJJRMGCXkdijMmc1cBPwHUiEgfgnOvhbUihtWUL1KypnaqPPAK7d0PfvnD1\n1dYbETSDB0P+/PDOO1C69MmPe/LJlCFLPr5xZmecAUOGaFEPY0ymBZJIjAX8Zr9yLHlf/fQPz7ne\neQeuuEJH25hs9vff2px3771eR2KMybyb0B6JWc65KcBoIKpqZm7cqI3bo0ZpIjFoEOzaBa++atVD\ng2LnTvj0U00QSpfWD3raNJ3v4PPMM7BvnyYcGWnVKrixGpODBZJI5BaRw74NETnsnIu6/r+4OL09\n+qidFIJixQo4+2yt0mSMiSgiMh4Y75wrBLQDegBlnXPvAeNFZJqnAYZAQoLez5+fsiByp0621lDQ\nlCihcyJ8pRMPHYIZM3Ti9Zln6r6vv4Zq1byL0ZgocEYAx2x3zrX1bTjnrgd2BC+k8DR1qt7bdW6Q\nXHMNbN5sFZmMiWAisk9EvhCRNkB5YBnwlMdhhcTmzSk/t26tw19fftm7eKJC48a69hDonAeAZcv0\n/t9/4c8/9RhjTNAEkkg8CDztnPvbOfc30Bt4ILhhhZ8pU/T7yhZVNsaYUxORnSLygYhc5XUsoZCQ\noMnDRReljKY591yvo8qhvvgCHngA9u9P2edLJJYu1fv58/XeEgljguqUiYSIrBORRmjZ1xgRucQ3\nme5UnHOtnHNrnHNxzrkTWqWcc28655Yl3/50zu32e+xO59za5NudmfmlstOxYzqJbtYsHUZpw5qC\n5JZbUhadM8aYCJOQoKMzBw6Ep56Cu+7yOqIcbNgw+OUXKFAgZV+5clrCdV7ykiUzZ2r5rHr1vInR\nmChxykTCOfeic66YiCSKyF7nXHHn3AsBPC8XMBS4Bk1COjrnavgfIyI9ROQiEbkIeBv4Jvm5JYD+\nQEOgAdDfOVc8s79cdrjxRj057Ntn87GCRgS+/x62b/c6EmOMyZLNm/Vccd118NJL1ugUNIcPw8KF\nuuJ02g/58st1kSfQCoCNGmV9kTpjTEACGdp0jYgc7ykQkV1A6wCe1wCIE5H1yZO1RwPXZ3B8R2BU\n8s8tgenJXeO7gOmAJ5fxCxbod9Mnn+i4V5OBBQt0Eknz5vDDD4E/b9s2zdTOPz94sRljTBD5eiRM\nkP32Gxw8mP6QpU8/hXbt9OdmzbSKkzEmqAJJJHI55/L5NpxzBYB8GRzvcw6wyW87PnnfCZxzFYHK\nwMzMPNc5d79zbrFzbvH2ILRm792r17itW2s3tS2qfApDh8JPP+kiP/4zD08lLnmknE1AMcZEqIQE\nOCfdM5xJV9euUKaMTj5cvz7w5/mGLqWXSOTLB0WK6M+FCtmaRMaEQCCJxOfAD865e51z96K9AyMD\neF56HbtykmNvBb4WkWOZea6IDBOReiJSr3RGi9Fk0bp1em/XtwFIStLSVjfcoPW90y7+kxFLJIwx\nESwxUauOWo9EgERg8mTNvDZvhjfeCPy5+fJB06ZQvnzw4jPGBCyQydaDgBeA6uhchynASdahTyUe\n8K9ZUR5IOMmxt5IyrCmzzw0au77NhGXL4J9/UiaSHDqkqzEFolAhHctaMZA/K2OMCQ/798Nll8E3\n3+i29UgEyDnthfjlF7jtNh07vHNnYM994AGdSG2MCQuBLEgHsBVIAtoDG4BxATxnEVDFOVcZ2Iwm\nC53SHuScqwoUB+b57Z4KvOg3wboF0CfAWLONL5GwofsBqFFDa+TWqwdHjmjT3D336Gqjp3LLLXoz\nxpgIsm6dXgv/9ZduW49EJjinQ48ef1wrLvmIpEyinj499Xw7Eeje3XojjAkjJ00knHMXohf/HYF/\ngTGAE5GmgbywiBx1znVDk4JcwHARWeGcGwgsFpGJyYd2BEaLiPg9d6dz7nk0GQEYKCIBNldkn7g4\nHcLpG3Jp0rF7t3ZRn3de6tX66tSB8eOhdm3d7xt6lpgI334LR4+mHHveeXDppaGN2xhjTtO//+p9\nfLzeWyIRoHfegd9/hw8+gNhYLf2dmKg92tddp/MnkpLg4YdhwwbInXypIgITJsCPP6ZOPowxnsmo\nR2I18BNwnW/dCOdcj8y8uIhMBian2fdsmu0BJ3nucGB4Zt4vu8XF2bCmU3rpJRg0CO67T4cn+dxw\nA3TrBrffDl26wIcf6v6334ann079Gnny6OpNDz8curiNMeY07diRetuGNgXo2291KKx/+dbChWHP\nHj0XPPigVjf59VcdImsr+xkTtjKaI3ETOqRplnPuQ+dcM9KfBJ1jrVtnicQpff+91sd9Ic3SIv/f\n3n3HSVXf+x9/fVwEFUVBhCAdAvZEkKDYNRZiVDRGxNgSMRhvrMSCSS4azb1qookx8VqiWHKjGPGn\nknvNIjFobChFLICYBVGWXmwUKcv3/vE589vZYWZ3B3bmnNl5Px+P8zhlzpz5eHacL5/zbaknSUOG\n+Ix+Kddc49XVVVW1y/vve8EhIlJCUolE27b+72DVXjdCCD77dGom6nQjR3qZ8PDDnlTsvLOSCJGE\ny1kjEUJ4GnjazFoDpwFXAR3N7B7g6RBCsx2gecwYr2WtrlYiUa+FC+Hdd70fRIcOdV8zgx49YNy4\n2mpp8O3jjitqmCIihZBKJH79a5g1K95YSsYrr/iNy5ZInH66lxsXXeS13RMmqJOiSMI12Nk6hLAG\n+DPw52jG6TOBUUCzTSTuuKO2UFAiUY8JE3xd35Tf6W1bJ070/hS/+AXsumvh4xMRKaCVK70WYvjw\nuCMpIZdf7uuBA7d8rUULH7Rj6lQfza9nz+LGJiJ5a+yoTYB3ggbui5Zma+lSaN/e+xFne2gikX79\n4Oc/h/32q/+8yy/3YU3at/dxEm+/vTjxiYgU0IoV/rMmefjd73z9jW9kf32vvXwRkZKQVyJRDjZt\n8qdMN94IV1/tD0WajTvugMce86c91gTdXfr186UhLVvC+GiQru9+t25TJxGREqVEIg9XXw0nnQTH\nHht3JCLShPQvugypNq8dOjSzJAL8hxy83VZDtQgNmT/fO5EcckjDicHIkdCmDdTUwPe2mEpERKQk\nNetE4sor4aCDfOS9bTVtmj/I6tRJiYRIM9PgzNblZtkyX2f2HS55S5fWbldWbvv1HnoIjjoKPv+8\n4XP33BNGj/a+EaqyFpFmYuXKZpxIfPEFjBgB69dv+7V++1sfgemii7b9WiKSKEokMjTbRKJDB5gx\nAw491Mcq3FaVlXDwwdCu3bZfS0SkBDXrGolhw+DLL2Hs2G27TnU1PPGEJxEaZEOk2VHTpgzNNpEw\n81mmX31126+1YgVMmeIdSUREytD69f7QvlkmEpWV8MYbXoN8yy0+KdzJJzd+GMPXXoPu3X2Gvnvv\n9VmqU6M1iUizokQiQ8kmEi+/7MnC4Yf7THrpE8SF4KMm3XabD7n36adw3XWwYUPda9x5Z+OeGN18\ns1+zvmFfRUSasZUrfb377vHGURDjxsEzz8Dvf+/92saNg2OOafz7zz7bm7MOHw59+/o1NJSrSLOk\nRCLD0qXed3i33eKOJE833AD77uuJxOefwz/+Uff1Fi18lr2aGjjrLJ9NOtOGDfDOO3DTTT5EX+fO\nta/9/vfQsSMMHeozVR9zjHfEExEpQ6lEolnWSKRmnj77bDjlFK9RaNOmce9dvhw+/rh25JJhw5qm\nw7aIJJISiQzLlnltRFOMjlo0q1f7bKEDBvh+v35eA5FLaiK5bJYsgaee8mH6LrzQj23e7MnF4MGe\nSPzlL00Xu4hICUr9O7nZJRLr18N77/loe+CdpEOA11+HjRvhyCPrf/9bb/k6NeFcy5aFi1VEYqdE\nIkMqkSgpkyb5D3xTNDXaf38fZamysjaRmD7dS80TT9z264uINAMlnUisXQurVtU9tuOO3k5r5kwv\nT9JnYzWDiy/2MdGffNKPVVT4cK6Zpk/3dWPmGBKRkqdRmzKUZCJRWek/8Icdtu3XMvOEYeJEn50v\ndX2AE07Y9uuLiGRhZoPNbI6ZVZnZqBznDDWzWWY208weSzt+gZn9K1ouKEa8qUSi5PpIhOAPjLp2\nrbtceqm/3qED7LDDlk1Xv/c9mDy59vyDD85+/enToVevEmwfLCJbQzUSGZYtK5GpDt55B3bZBXr0\ngL/9zSf5adWqaa49eLDPEzFlCgwa5E+gDjqoBDMsESkFZlYB3A0cD1QDU8xsfAhhVto5fYDrgcNC\nCJ+YWYfoeDvgBmAAEIBp0Xs/KWTMJdvZOgSfHO7FF+GAA2qP9+rl6y5dvKN1795133f55V5bnRqk\nIzVj6/z5XpvRsaPvn3OOBuIQKSNKJDKURI3EBx/4UK5XXeUFwjPP1NYeNIXjjvOOdalCpndv+NrX\nmu76IiJ1DQSqQgjzAMxsLDAEmJV2zg+Bu1MJQgghGmOPE4GJIYRV0XsnAoOBxwsZ8IoV/jNZcl0A\nttsOTj/dl1yyNWPdaSc4//y6x1at8idvV1wBv/qVHxsypOliFZHEUyKRZs0abzqa+ETiued8feqp\n3hSpqf+R364dTJ3qnewAbr0VunVr2s8QEanVGViQtl8NZLad6QtgZq8CFcCNIYTKHO/tTIGtWFGC\ntRHgcwnV1DTcabox2rXzhOS//ssnPN20CX7+c792C/3zQqQc6P/0NEuX+jrxicSECbD33nD00YX7\njD59arf79i3c54iIQLZx8kLGfgugD3A00AV42cz2b+R7MbMRwAiAbk3wYKRkZ7W+5RYf1e/dd5vm\nev/+7z7a3+rVvn/HHT5iU+pBlIg0a0ok0pTEZHTr1nnb1osvjjsSEZGmUg10TdvvAizKcs7kEMJG\n4EMzm4MnFtV4cpH+3hczPyCEcD9wP8CAAQO2SDTytXIl7LHHtl4lBtOnw/HHN9319tvPyyQRKUsa\ntSnNkiW+TnQi8c47Xn2szmwi0nxMAfqYWU8zawkMA8ZnnPMMcAyAmbXHmzrNAyYAJ5hZWzNrC5wQ\nHSuokqyRWLzYl/ShXUVEtoFqJNLMm+fr1OAViXTwwf4obIcd4o5ERKRJhBA2mdmleAJQAYwJIcw0\ns5uAqSGE8dQmDLOAGuCaEMJKADO7GU9GAG5KdbwupJLsI/HQQ75WIiEiTUSJRJqqKmjb1vuPJVqb\nNnFHICLSpEIIzwHPZRwbnbYdgJHRkvneMcCYQseYsn69dwkoiRqJ22+H7t3hzDN9RJE2beDAA+OO\nSkSaCTVtSlNVBV/9atxR1OPjj+Goo+DNN+OORESkbKXmkEh8IrFiBYweDc8/7/ujRsHChT4HkYhI\nE1AikSbxiURlJfzznyoERERiVDKzWt93nw/QceWVvr/LLhpNSUSalBKJyIYNPiJeohOJCRN8Poe9\n9447EhGRspVKJBJdI7F+PfzhDz4wx377xR2NiDRT6iMRmT8fNm+OOZGYNg2efRZatYJLL4Vdd619\n7Xe/g4kTYdgwn4RORERiURKJxNixPhThyC26lIiINBklEpGqKl/HmkgsWQIPPgiLFsFOO8FVV9W+\nds89Xm0ybFh88YmISGn0kdhtNzjjDDjuuLgjEZFmTE2bIolIJL79be8It/fe3owp3fvvw5dfwrHH\nxhObiIgAtTUSsY3wF0LtTNK5DBkC48apBltECkqJRKSqyvugxTZT6dy53r4KvE3rSy95J7nNm+FX\nv/KJ6EREJHYrVvgoqi1bxhTAK69A584weTKsWgUfflj39Zdegs8+iyc2ESkrSiQiqRGbYnt485//\nCf36QU0NnHiiZzTz5nkzp+uug9deiykwERFJF/us1r/5DWy/PRxwABx0EFxxRe1ra9d67fa118YX\nn4iUDSUSkYULfUCkotu40ROGCRPg+OOhogJOOMGHkNpvv4S0uRIRkZTYEokPPoCnn/ZBOS65BFq3\nhgsugL/+1V8D316zRv3pRKQolEhE1q6NaXjtiy+G3r09k/nWt/zYdtt51UgISiRERBJm5coY5pCo\nqYELL4TvfMfbVP3bv/nxSy6BFi3g4Yd9/7HHYM894cgjixygiJQjJRKRtWt9oKSi23lnOO00H6rv\nnHNqj0+YAJ06wd//7lXYXbvGEJyIiGSKpUaiogJeeAEmTfI+c506+fGOHb2J09SpPiBHZSWceaaf\nLyJSYBr+NRJbInHXXdmPd+8OS5fCE09A374qFEREEiK2pk2tWsHRR295vH9/eOYZeOstHyY82zki\nIgWgGolIbIlELnvt5Z02vv1tePHFuKMRERH8of+aNUVo2vT4497s9cc/9v1zzoHhw7Ofe/31PoLT\nIYd4nzvNHSEiRaJEAti0yR/iFD2ReO016NEDpkzZ8jUzHwb2n/9M+KxHIiLlIzUZXUETiZoa+NnP\nvJ/c3nv7+vnnfTjwbHr3rh12sGfPmDr8iUg5UiKBT9cARUwk7rwTbrnFR9n46KPcsxoNGgRffOHN\nm0REJHap6Rnati3ghzz7rM8N8etfw2WXwZw53p5q0KDc77nvPu9L9/e/FzAwEZG6lEhQm0jsuGOR\nPnDiRPjlL2HmTO/7kGvc2aFDvSr7iCOKFJiIiNQnNaF0QR/6/+Y3XrNw2mleG7HPPn68vkTi2muh\nuhpmzy5gYCIidamzNd4/AopQI3HrrdChA/zoR/Dcc/DII960afvts5+/007wwAMFDkpERBqr4InE\n+vXQq5cP9ZoaZOO22+DBB2Hffet/H9SfbIiINDHVSFCkRKKmxqupX34ZjjnGk4flyzU/hIhICSl4\nItGqFTz6qCcSKdde682b6hu979FHfZCOr3+9QIGJiGxJiQRFSiSmToVVq+DEE70ESjVXOvnkAn6o\niIg0pYInEmvWeHOmfA0dCu+/n7uGW0SkANS0iSIlEpWVPqLG8cf7/ne+A7vsAiNGFPBDRUSkKRU8\nkTjvPFi0yIdzFRFJONVIkEci8cwz8MYbW/chlZUwcGDtmIE//rFfr2XLrbueiIgUXaMTidWrfQbq\nfE2f7h2tRURKgBIJ8kgkrrgC7rkn/w/YtAl22EHNmERESlwqkWjduoEThw71UZdqahp/8ZUrfUjw\n/v23Oj4RkWIqaCJhZoPNbI6ZVZnZqBznDDWzWWY208weSzteY2YzomV8IeNsVCIRAnz8sY+09Oyz\n+X1AixYwaZJPMCQiIiVr9Wp/LtSivobBb73lTVc//NBrnhvrrbd8rURCREpEwRIJM6sA7ga+BewL\nnG1m+2ac0we4HjgshLAfcGXay+tCCAdGy6mFihMamUh8+mnt9pNP5vcBGzf62iy/94mISKKsXt2I\nZk1jx8JTT0H37vDb39Z/bmUlvPKKb7/5pq/79dvmOEVEiqGQNRIDgaoQwrwQwgZgLDAk45wfAneH\nED4BCCEsK2A8OTUqkViyxNctWsCECbB5c/0XranxBGLDBujTB268sSlCFRGRGDUqkZgzx4diPe88\neP312kImmyef9KHBAY47DkaOhHbtmixeEZFCKmQi0RlYkLZfHR1L1xfoa2avmtlkMxuc9toOZjY1\nOn5aAeP8/zNbNyqRuOACWLGitgo6m3nzoE0b70jdqpW3ec01e7WIiJSMRiUSH3wAffvCgAH+0Km+\nTteLFvkCPiDHHXc0WawiIoVWyOFfs7XjyRwcuwXQBzga6AK8bGb7hxA+BbqFEBaZWS/gH2b2bghh\nbp0PMBsBjADotg3/UF+7FrbbroHhtw8+GGbM8HavDz7o1dEHHZT93E2b/MlS27ZeG7HjjnD22Vsd\nn4iIJEODiURNDVRVwSmnwDe/6Q+WevTIff7ChRqlSURKViETiWqga9p+F2BRlnMmhxA2Ah+a2Rw8\nsZgSQlgEEEKYZ2YvAv2AOolECOF+4H6AAQMGbMUMPm7tWq+NqLcLw0471c4YOmSI97bLpW/f/Dtk\ni4hI4jWYSHz0kTdr7dvXT2yo+mLhQjj88CaNUUSkWArZtGkK0MfMeppZS2AYkDn60jPAMQBm1h5v\n6jTPzNqaWau044cBswoVaCqRqNfEiV4TAT4Kx09+kvvcNWu8VkJERJqVBhOJHj1g/nyfdBRg/HgY\nPTr7uV9+CatWQefMVr8iIqWhYIlECGETcCkwAZgN/CWEMNPMbjKz1ChME4CVZjYLmARcE0JYCewD\nTDWzt6Pjt4YQ4k0k/vu/4aabavc3b4Yvvsh+7rXXQqdOTRafiIgkQ4OJxHbb+WhNbdv6/uuvw623\nwvr12c9/5BFvBiUiUoIK2bSJEMJzwHMZx0anbQdgZLSkn/MacEAhY0vXqERiyRL4yld8OwTYd1+o\nqIBDD4Xbb4ddd617bocOBYtXRETi0WAi8ac/+UmXXOL7/ft7U6c33oBBg+p2xtthBzj//ILGKyJS\nSJrZmq1IJMzgrLO8CdMDD/hkc7nOFRGRZqPeRKKmxmukJ06sPZYalOOoo7yfXUjrzrdggddYbNhQ\nsHhFRApJiQRbkUgA/OIXtUPAVlXVf66IiJS8mhofLjxnIvHSS/77nz5KX69e8MQTcM45cNlldecg\nevJJr9Wub54JEZEEK2jTplKxdm0D8/9s2gTLl2+ZHLRtC7vv7k+V0i1dqkRCRKSZWbPG1zkTicce\n8xdPPrnu8aFDfcm0cKEPD57eNFZEpIQokcATiS5d6jmhosITie2yVOB8+KHPLZFSUwM//anPOyEi\nIs3G6tW+zppIbNgATz0Fp5/uyUE2c+f6KE3f+IbvL1zoIzbVO/a4iEhyKZHAq6pzNm1auBDee88n\nluvVa8vX05MI8KTjpz9t8hhFRCRe9SYSS5bAXnvBmWfmvsDw4f7k6s03fX/RIthzzyaPU0SkWNRH\nggb6SAwdCoMHe1vWbCZNgu99r3Zovy++8KZOmkdCRKRZqTeR6NYNJk+ufyjXQYO8b926db6fqpEQ\nESlRSiTwRCJrTXQIMHOmP2E699zsb164EB5/3CcgAqis9AJl9uxChSsiIjGoN5FI70Sdy6BB/pDp\n3nt9kI6HHoKrr27SGEVEikmJBPXUSKxaBZ995j/+uZ4affWrvk6N3LRkia/V2VpEpFHMbLCZzTGz\nKjMbleX175vZcjObES0Xpb1Wk3Z8fCHjzNnZOgTo29dH86vPoYdCy5YwcqT3pzjySJ9nQkSkRJV9\nH4mNG33JmkikkoNUspBN7951z1261PtJ7L57k8YpItIcmVkFcDdwPFANTDGz8SGEWRmnPhFCuDTL\nJdaFEA4sdJxQT43EggXekbre4f+A9u1hzhxYsUJNmkSkWSj7RCLVVDVrItGhA9xwAxxYTxnVvj20\naVO3RqJjx+wjPImISKaBQFUIYR6AmY0FhgCZiUTsciYSkyf7etCghi/So4cvIiLNQNn/azc1D1DW\nRKJnT7jxRujaNfcFzLzG4thjff/ttz2REBGRxugMpE/GUx0dy3SGmb1jZuPMLP1HeQczm2pmk83s\ntEIGWm8iseOOPnO1iEgZKfsaiXoTiblzYbfdGm6mdPPNtYnEddd5G1gREWmMbJMohIz9vwKPJ60n\nSQAAD5JJREFUhxDWm9mPgEeA6EeXbiGERWbWC/iHmb0bQpi7xYeYjQBGAHTr1m2rAs2ZSMyeDfvs\nA9tvv1XXFREpVUok6kskzj3XX3jhhfovctJJtdvf/W6TxSYiUgaqgfQahi7AovQTQggr03b/CNyW\n9tqiaD3PzF4E+gFbJBIhhPuB+wEGDBiQmag0yurV0KJFlmdFJ53kHa5FRMqMmjbVl0hUVdXf0VpE\nRLbVFKCPmfU0s5bAMKDO6Etm1ilt91RgdnS8rZm1irbbA4dRwL4Vq1d7bcQWE1FfdhlcfnmhPlZE\nJLHKvkYiZ2frTz/1kTWUSIiIFEwIYZOZXQpMACqAMSGEmWZ2EzA1hDAeuNzMTgU2AauA70dv3we4\nz8w24w/Gbs0y2lOTWb0aWrfOOLh+vY8L29CITSIizVDZJxI5ayTmRjXjSiRERAoqhPAc8FzGsdFp\n29cD12d532vAAQUPMNK6tY/BUcerr8I3vwmTJsHRRxcrFBGRRCj7RGLjRmjVKsvM1kokREQkzV13\nZTk4b56vt8gwRESav7JPJE49Fb78MssLAwfCAw8okRARkdzmzvXRmrp0iTsSEZGiK/tEIqcePWD4\n8LijEBGRJJs718uLioq4IxERKbqyH7Upp0mT4IMP4o5CRESSbO5c6N077ihERGKhGolczjkHBg+G\nMWPijkRERJLqqqtgl13ijkJEJBZKJLJZswYWL1b/CBERqd+558YdgYhIbNS0KZvUKBxKJEREJJfP\nPoO3366dkEhEpMwokcimqsrXavcqIiK5vPIKHHigJxMiImVIiUQ2SiRERKQhCxf6unPneOMQEYmJ\n+khkc/750K8f7LZb3JGIiEhSLVoEZvCVr8QdiYhILJRIpITgBQJAx46+iIiI5LJwIXTo4BPSiYiU\nITVtStl/f/jhD3370Udh8uR44xERkWRbtEjNmkSkrKlGImXZstqnSpddBj/4ARxySLwxiYhIco0a\n5cOFi4iUKdVIAGzeDCtWwD33wNq18PnnavMqIiL1O+IIn7hURKRMKZEA+OKL2u2PPvK1EgkREcll\n40b4n//x5k0iImVKiQTAqlW126nxwJVIiIhILosWwSmnwN/+FnckIiKxUSIB0KoV9Ojh2zNm+Fqj\nNomISC6pOST23DPeOEREYqREArwgGDfOt/v1gzlzYJ994o1JRESSS5PRiYho1CYANmzwscD794c2\nbaBv37gjEhGRJNq82depvhGqkRCRMqYaCYAHH4Ru3eB//xe+/BIeeCDuiEREJImmTfPmsA8/7Ovd\nd487IhGR2KhGAmo7W7dt64XD/Plw0UVxRiQiIklUXQ2bNsHo0bDrrmAWd0QiIrFRjQR4ItG6tU9C\nN368RmwSEZHsqqt9ffjhcOyx8cYiIhIzJRIAn3zitRHLlvm+EgkREclmwQJv0tS+fdyRiIjETokE\neI1Eu3a1Q74qkRARkWyqq6FLFzVpEhFBfSTcGWf47NbTpvm+5pAQEZFsBg/2Ef5ERESJBADnnefr\n22/39dlnxxeLiIgk1/nnxx2BiEhiqGkT+MRC69bB/vvDKadARUXcEYmISNJs3gyLF9fOJSEiUuaU\nSIQAvXvDjTd6lfX48T45nYiISLqlS30CunvvjTsSEZFEUCKxbh2sX++drUVERHJJDf3atWu8cYiI\nJERBEwkzG2xmc8ysysxG5ThnqJnNMrOZZvZY2vELzOxf0XJBwYJMTUanREJEROqzYIGvu3SJNw4R\nkYQoWGdrM6sA7gaOB6qBKWY2PoQwK+2cPsD1wGEhhE/MrEN0vB1wAzAACMC06L2fNHmgSiRERKQx\nVCMhIlJHIWskBgJVIYR5IYQNwFhgSMY5PwTuTiUIIYRoRjhOBCaGEFZFr00EBhckSiUSIiLSGKnJ\n6HbfPe5IREQSoZDDv3YGFqTtVwMHZ5zTF8DMXgUqgBtDCJU53ts58wPMbAQwAqBbt25bF2XPnj7s\n6157bd37RUSkPJx8MnTrpsnoREQihUwksv3Shiyf3wc4GugCvGxm+zfyvYQQ7gfuBxgwYMAWrzdK\n9+7wk59s1VtFRKSMHHWULyIiAhS2aVM1kN6QtAuwKMs5z4YQNoYQPgTm4IlFY94rIiIiIiIxKWQi\nMQXoY2Y9zawlMAwYn3HOM8AxAGbWHm/qNA+YAJxgZm3NrC1wQnRMREREREQSoGBNm0IIm8zsUjwB\nqADGhBBmmtlNwNQQwnhqE4ZZQA1wTQhhJYCZ3YwnIwA3hRBWFSpWERERERHJTyH7SBBCeA54LuPY\n6LTtAIyMlsz3jgHGFDI+ERERERHZOprZWkREYtfQBKZm9n0zW25mM6LlorTXijOBqYiI1FHQGgkR\nEZGGNGYC08gTIYRLM95bvAlMRUSkDtVIiIhI3BozgWkuxZvAVERE6lAiISIicWvUJKTAGWb2jpmN\nM7PUEOGNfa+IiDQxJRIiIhK3xkxC+legRwjha8DfgUfyeC9mNsLMpprZ1OXLl29TsCIi4pRIiIhI\n3BqchDSEsDKEsD7a/SNwUGPfG73//hDCgBDCgD322KPJAhcRKWdKJEREJG4NTmBqZp3Sdk8FZkfb\nmsBURCQm5lM5lD4zWw58tBVvbQ+saOJwmkIS40piTKC48pHEmEBx5WNbYuoeQkjk43gzOwm4k9oJ\nTP8jfQJTM7sFTyA2AauAS0II70fvvRD4aXSp/wghPNTAZzWn8iKJMYHiykcSYwLFlY8kxgRFKC+a\nTSKxtcxsaghhQNxxZEpiXEmMCRRXPpIYEyiufCQxpnKRxHufxJhAceUjiTGB4spHEmOC4sSlpk0i\nIiIiIpI3JRIiIiIiIpI3JRJwf9wB5JDEuJIYEyiufCQxJlBc+UhiTOUiifc+iTGB4spHEmMCxZWP\nJMYERYir7PtIiIiIiIhI/lQjISIiIiIieVMiISIiIiIieSvbRMLMBpvZHDOrMrNRMcbR1cwmmdls\nM5tpZldEx280s4VmNiNaToohtvlm9m70+VOjY+3MbKKZ/Staty1iPHul3Y8ZZva5mV0Zx70yszFm\ntszM3ks7lvXemLsr+q69Y2b9ixzXr83s/eiznzaz3aLjPcxsXdp9u7fIceX8u5nZ9dH9mmNmJxYx\npifS4plvZjOi48W8V7l+E2L/fpUrlRcNxpWosiL6fJUX+ceksiK/uGItLxJTVoQQym7BJzyaC/QC\nWgJvA/vGFEsnoH+0vQvwAbAvcCNwdcz3aT7QPuPYr4BR0fYo4LYY/4ZLgO5x3CvgSKA/8F5D9wY4\nCfgbYMAhwBtFjusEoEW0fVtaXD3Sz4vhfmX9u0Xf/7eBVkDP6P/VimLElPH6HcDoGO5Vrt+E2L9f\n5biovGhUXIktK9L+hiovGo5JZUUecWW8XvTyIillRbnWSAwEqkII80IIG4CxwJA4AgkhLA4hTI+2\nvwBmA53jiKWRhgCPRNuPAKfFFMc3gbkhhK2ZnXabhRD+ic+umy7XvRkCPBrcZGA3M+tUrLhCCM+H\nEDZFu5OBLoX47HzjqscQYGwIYX0I4UOgCv9/tmgxmZkBQ4HHm/pzG1LPb0Ls368ypfJi6ySlrACV\nF42KSWXF1sUVV3mRlLKiXBOJzsCCtP1qEvBjbGY9gH7AG9GhS6PqpzHFrhaOBOB5M5tmZiOiYx1D\nCIvBv8RAhxjiAhhG3f9p475XkPveJOn7diH+RCKlp5m9ZWYvmdkRMcST7e+WhPt1BLA0hPCvtGNF\nv1cZvwml8P1qjhJ5fxNWXiS5rACVF1tDZUXjxV5exFlWlGsiYVmOxToOrpntDDwFXBlC+By4B+gN\nHAgsxqvNiu2wEEJ/4FvAj83syBhi2IKZtQROBZ6MDiXhXtUnEd83M/sZsAn4c3RoMdAthNAPGAk8\nZmZtihhSrr9bEu7X2dT9h0fR71WW34Scp2Y5pnG9m07i7m8Cy4tElhWg8mKrAlBZka9Yy4u4y4py\nTSSqga5p+12ARTHFgpltj38J/hxC+H8AIYSlIYSaEMJm4I8UqLquPiGERdF6GfB0FMPSVFVYtF5W\n7Ljwwmp6CGFpFF/s9yqS697E/n0zswuAk4FzQtRYMqoOXhltT8Pbl/YtVkz1/N1ivV9m1gL4DvBE\nWqxFvVfZfhNI8PermUvU/U1ieZHgsgJUXuRFZUV+4i4vklBWlGsiMQXoY2Y9o6cVw4DxcQQSta17\nEJgdQvhN2vH0dmunA+9lvrfAcbU2s11S23gnrPfw+3RBdNoFwLPFjCtSJ/uP+16lyXVvxgPnRyMm\nHAJ8lqp2LAYzGwxcB5waQlibdnwPM6uItnsBfYB5RYwr199tPDDMzFqZWc8orjeLFRdwHPB+CKE6\ndaCY9yrXbwIJ/X6VAZUX9ceU5LICVF40msqKrRJbeZGYsiIUoRd+Ehe89/oHeKb4sxjjOByvWnoH\nmBEtJwF/At6Njo8HOhU5rl74aAhvAzNT9wjYHXgB+Fe0blfkuHYCVgK7ph0r+r3CC6bFwEY8yx+e\n697g1Yl3R9+1d4EBRY6rCm8Xmfp+3Rude0b0t30bmA6cUuS4cv7dgJ9F92sO8K1ixRQdfxj4Uca5\nxbxXuX4TYv9+leui8qLemBJZVkQxqLzILyaVFXnEFR2PrbxISllh0cVFREREREQarVybNomIiIiI\nyDZQIiEiIiIiInlTIiEiIiIiInlTIiEiIiIiInlTIiEiIiIiInlTIiHSADOrMbMZacuoJrx2DzOL\naxxzERFpQiovpNy0iDsAkRKwLoRwYNxBiIhI4qm8kLKiGgmRrWRm883sNjN7M1q+Gh3vbmYvmNk7\n0bpbdLyjmT1tZm9Hy6HRpSrM7I9mNtPMnjezHWP7jxIRkSan8kKaKyUSIg3bMaOq+qy01z4PIQwE\n/gDcGR37A/BoCOFrwJ+Bu6LjdwEvhRC+DvTHZ74E6APcHULYD/gUnxVTRERKj8oLKSua2VqkAWa2\nOoSwc5bj84FjQwjzzGx7YEkIYXczWwF0CiFsjI4vDiG0N7PlQJcQwvq0a/QAJoYQ+kT71wHbhxB+\nWfj/MhERaUoqL6TcqEZCZNuEHNu5zslmfdp2Deq7JCLSHKm8kGZHiYTItjkrbf16tP0aMCzaPgd4\nJdp+AbgEwMwqzKxNsYIUEZHYqbyQZkeZrEjDdjSzGWn7lSGE1JB+rczsDTwpPzs6djkwxsyuAZYD\nP4iOXwHcb2bD8SdJlwCLCx69iIgUi8oLKSvqIyGylaI2rwNCCCvijkVERJJL5YU0V2raJCIiIiIi\neVONhIiIiIiI5E01EiIiIiIikjclEiIiIiIikjclEiIiIiIikjclEiIiIiIikjclEiIiIiIikrf/\nA7ch1hc8kra8AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1c33c85a908>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 绘制训练曲线            \n",
    "plt.figure(figsize=(13, 5))\n",
    "x_plot = np.arange(len(train_acc)) + 1\n",
    "\n",
    "plt.subplot(121)\n",
    "plt.plot(x_plot, train_acc, color='blue', label='train acc')\n",
    "plt.plot(x_plot, test_acc, color='red', ls='--', label='test acc')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(122)\n",
    "plt.plot(x_plot, train_auc, color='blue', label='train AUC')\n",
    "plt.plot(x_plot, test_auc, color='red', ls='--', label='test AUC')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('AUC')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "deepnote": {},
  "deepnote_app_layout": "article",
  "deepnote_execution_queue": [],
  "deepnote_notebook_id": "8930ea4aee414a189695f7cc8680b391",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
